import UIKit

//泛型通常用来表达一种未定的数据类型。举个例子，在编写函数时，如果这个函数有参数，开发者就需要明确参数的类型。如果要实现一个函数，其功能将对两个inout且类型相同的参数进行值的交换。这个函数的功能需求并没有要求参数具体的类型，只是要求需要类型相同，对于这类的需求，开发者不会也无法使用重载的方式将所有数据类型对应的函数都实现一遍，而泛型刚好可以解决这类问题
//定义泛型T
func exchange<T>(param1:inout T,param2:inout T) {
    let tmp = param1
    param1 = param2
    param2 = tmp
}

var p1 = 1
var p2 = 4

exchange(param1: &p1, param2: &p2)
print("p1:\(p1),p2:\(p2)")


//使用泛型实现一个特定类型的栈
struct Stack<T>{
    //每部元素类型的操作都使用T
    var items:[T] = []
    mutating func push(param:T){
        self.items.append(param)
    }
    mutating func pop() -> T{
        return self.items.removeLast()
    }
}

//整型栈
var stack1 = Stack<Int>()

stack1.push(param: 3)
stack1.push(param: 5)

print(stack1.pop())
print(stack1.pop())

//字符串栈
var stack2 = Stack<String>()

stack2.push(param: "hello")
stack2.push(param: "world")
print(stack2.pop())
print(stack2.pop())


//在Swift语言中，可以通过两种方式对泛型进行约束：一种是通过继承基类或者遵守协议来进行约束，另一种是通过where子句来进行约束
//定义一个基类
class SuperClass {
    
}

//只有SuperClass的类或者其子类才可以成为Stack栈中的元素
struct SuperStack<T:SuperClass>{
    var items:[T] = []
    mutating func push(param:T){
        self.items.append(param)
    }
    mutating func pop()-> T {
        return self.items.removeLast()
    }
}

//使用遵守协议的方式约定泛型。协议也是Swift语言中的一种重要结构，在Objective-C中也有协议的概念，也类似于Java语言中的接口。协议中可以定义一些没有实现的方法或者属性，遵守这个协议的类型需要对其中定义的方法和属性进行实现
protocol MyProtocol {
    
}

//定义一个栈,只有遵守了MyProtocol的类型才可以作为栈中的元素
struct ProtocolStack<T:MyProtocol>{
    var items:[T] = []
    mutating func push(param:T){
        self.items.append(param)
    }
    mutating func pop() -> T {
        return self.items.removeLast()
    }
}

